/*
 * Judge.cpp
 *
 *  Created on: 2012-11-17
 *      Author: kidlet
 */

#include "head.h"
#include "var.h"

int main(int argc, char** argv)
{

	if ( argc==0 )
	{
		printf ( "%s 执行Judger参数有误， 请附加上SolutionId参数\n",GetDateTime ("%Y-%m-%d %X%p") );
		return 0;
	}

	sscanf ( argv [0], "%d", &solutionid );
	solutionid = 1001;
	if (Intial ())
	{
		printf( "%s 初始化程序失败, 请检查配置文件\n", GetDateTime ("%Y-%m-%d %X%p") );
		//Write log
		return 0;
	}
	//cout <<Execute_SQL ( "INSERT INTO queue (solution_id,in_date) VALUES ('1002',NOW())");
	//cout <<mysql_error (mysql);
	//return 0;
	if ( CheckSolutionID ( solutionid ) )
	{
		printf( "%s 读取solution: %d 失败，请检查数据库\n", GetDateTime ("%Y-%m-%d %X%p"), solutionid );
		//Write log
		return 0;
	}

	if ( UpdataTestData ( problemid ) )
	{
		printf( "%s 更新或者读取 题号为%d的测试数据文件出错，请检查文件目录和数据库\n", GetDateTime ("%Y-%m-%d %X%p"), problemid );
		//Write log
		return 0;
	}

	if ( CreateWorkDir () )
	{
		printf( "%s 创建临时目录失败\n", GetDateTime ("%Y-%m-%d %X%p") );
		//Write log
		return 0;
	}

	if ( ComplieSourceFile () )
	{
		printf( "%s 读取源代码和编译过程中出错了\n", GetDateTime ("%Y-%m-%d %X%p") );
		//Write log
		return 0;
	}
	int T,M;
	int ret= Run ( T, M );
	//cout << ret << ' '  << t << "ms "<< m << "kb" << endl;
	//Execute_CMD ( "rm -R %s/Tmp/%s", OJ_Home, Work_Dir );	//清除临时文件和目录
	if ( ret!= OJ_AC )
	{
		sprintf ( str_tmp, "UPDATE think_solution SET result=%d, judgetime=NOW() time=0, memory=0 WHERE solution_id=%d", ret, solutionid );
		mysql_query ( mysql, str_tmp );	//更新状态

		//sprintf ( str_tmp, "DELETE FROM think_queue WHERE solution_id=%d", solutionid );
		//mysql_query ( mysql, str_tmp );	//删除队列任务

		Execute_CMD ( "rm -R %s/Tmp/%s", OJ_Home, Work_Dir );
		mysql_close ( mysql );
		return 0;
	}

	ret = Compare ();
	sprintf ( str_tmp, "UPDATE think_solution SET result=%d, time=%d, memory=%d, judgetime=NOW() WHERE solution_id=%d", ret, T, M, solutionid );
	mysql_query ( mysql, str_tmp );	//更新状态

	//sprintf ( str_tmp, "DELETE FROM think_queue WHERE solution_id=%d", solutionid );
	//mysql_query ( mysql, str_tmp );	//删除队列任务

	Execute_CMD ( "rm -R %s/Tmp/%s", OJ_Home, Work_Dir );
	mysql_close ( mysql );
	return 0;
}

int Intial ()
{
	//------------------- 读取配置文件参数----------------------------------
	int ret=0;
	ret += ReadParameter ( "Mysql_Address" , Mysql_Address );
	ret += ReadParameter ( "Mysql_Username" , Mysql_Username );
	ret += ReadParameter ( "Mysql_Password" , Mysql_Password );
	ret += ReadParameter ( "OJ_Home" , OJ_Home );
	ret += ReadParameter ( "Database" , DB );

	if ( ret!=0 )
	{
		printf("%s 读取配置信息出错\n", GetDateTime ("%Y-%m-%d %X%p") );
		//Write log
		return 1;
	}
	mysql = NULL;
	if ( Intial_MySQL () )
	{
		printf("%s Judger连接数据库出错\n", GetDateTime ("%Y-%m-%d %X%p") );
		//Write log
		return 1;
	}

	signal(SIGQUIT, Call_Exit );
	signal(SIGKILL, Call_Exit );
	signal(SIGTERM, Call_Exit );
	return 0;
}

int Intial_MySQL ()
{
	//------------------- MySQL数据库链接--------------------------------
	if ( mysql != NULL )
		mysql_close(mysql);

	mysql = mysql_init( NULL );	//如果参数为NULL 则分配一个
	if ( mysql == NULL )
		return 1;
	const char timeout = 30;
	mysql_options( mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);

	if ( mysql_real_connect ( mysql, Mysql_Address, Mysql_Username, Mysql_Password,  DB, 0, NULL, 0 ) == NULL )
		return 1;
	return mysql_query( mysql, "set names utf8" );	//设置获取字符集 以适应中文
}

int ReadParameter ( const char pName [BUFFER_SIZE], char result [BUFFER_SIZE])
{
	FILE* file;
	file = fopen ( CONF, "r" );
	if ( file == NULL )
	{
		printf("%s 打开配置文件失败\n", GetDateTime ("%Y-%m-%d %X%p"));
		//Write log
		return 1;
	}

	char str [BUFFER_SIZE];
	char name [BUFFER_SIZE], value [BUFFER_SIZE];
	int flag=0, i;	//标志是否找到
	memset ( value, 0, sizeof(value));		//初始化为空

	while ( fgets ( str, BUFFER_SIZE, file ) )
	{
		if ( str [0] == '#' )				//跳过注释行
			continue;

		memset ( name, 0, sizeof(name));	//初始化为空

		for ( i=0; str[i]; i++ )
			if ( str [i] == '=' )	break;
		if ( str [i] == 0 )					//找不到“=”
			continue;

		for ( int j=0; j<i; j++ )
			name [j] = str [j];
		name [i] = 0;

		if ( strcmp ( name, pName ) == 0 )
		{
			flag = 1;
			for ( int j=i+1; str[j]; j++ )
				value [j-i-1] = str [j];
			break;
		}
	}

	if ( flag==0 )
		return 1;

	value [strlen(value)-1] = 0;			//去掉结尾多余的 \n;
	strcpy ( result, value );
	fclose ( file );
	return 0;
}

const char*  GetDateTime ( const char* format )
{
	time_t a = time(NULL);
	strftime ( str_tmp, BUFFER_SIZE-1, format, localtime ( &a ) );
	return str_tmp;
}

int CheckSolutionID ( int id )
{
	MYSQL_RES *res;
	MYSQL_ROW row;

	char query [BUFFER_SIZE];
	int ret;

	sprintf ( query, "SELECT problem_id FROM think_solution where solution_id=%d", id );

	ret = mysql_query ( mysql, query );
	if ( ret )	return 1;

	res = mysql_store_result( mysql );
	int cnt = 0;
	cnt = res->row_count;

	if ( cnt == 0 )		//找不到对应的 solution 记录
	{
		mysql_free_result ( res );
		return 1;
	}

	//------------------------------------------------------
	row = mysql_fetch_row(res);

	sprintf ( query, "SELECT problem_id FROM think_problem where problem_id=%s", row [0] );
	mysql_free_result ( res );

	ret = mysql_query ( mysql, query );
	if ( ret )	return 1;

	res = mysql_store_result( mysql );
	cnt = res->row_count;

	if ( cnt == 0 )		//找不到对应的 problem 记录
	{
		mysql_free_result ( res );
		return 1;
	}
	row = mysql_fetch_row(res);

	sscanf ( row[0], "%d", &problemid );
	mysql_free_result ( res );
	return 0;
}

int UpdataTestData ( int pid )	//判断是否已经存在数据文件 或者 数据文件需要更新
{
	MYSQL_RES *res;
	MYSQL_ROW row;
	char query [BUFFER_SIZE];
	FILE *file=NULL;
	int ret=-1;

	sprintf ( query, "SELECT input,output,update_time FROM think_testdata WHERE problem_id=%d", pid );
	ret = mysql_query ( mysql, query );
	if ( ret )
	{
		//write_log
		return 1;
	}
	res = mysql_store_result( mysql );
	row = mysql_fetch_row(res);

	sprintf ( str_tmp, "%sData/%d/update.txt", OJ_Home, pid );
	file = fopen ( str_tmp, "r" );
	if ( file == NULL )
	{					//从数据库下载数据

		sprintf ( str_tmp, "%s/Data/%d", OJ_Home, pid );

		if ( opendir ( str_tmp )==NULL )	//还没有对应目录 则创建
			mkdir ( str_tmp, 0777 );

		sprintf ( str_tmp, "%s/Data/%d/update.txt", OJ_Home, pid );
		file = fopen ( str_tmp, "w" );
		fputs ( row [2], file );
		fclose ( file );

		sprintf ( str_tmp, "%s/Data/%d/input.in", OJ_Home, pid );
		file = fopen ( str_tmp, "w" );
		fputs ( row [0], file );
		fclose ( file );

		sprintf ( str_tmp, "%s/Data/%d/output.out", OJ_Home, pid );
		file = fopen ( str_tmp, "w" );
		fputs ( row [1], file );
		fclose ( file );

		mysql_free_result ( res );
		return 0;
	}
	else
	{
		char tmp [BUFFER_SIZE];
		fgets ( tmp, BUFFER_SIZE-1, file  );
		fclose ( file );
		if ( strcmp ( tmp, row [2] )==0 )
		{
			sprintf ( str_tmp, "%sData/%d/input.in", OJ_Home, pid );

			file = fopen ( str_tmp, "r" );

			if ( file == NULL )
			{
				file = fopen ( str_tmp, "w" );
				fputs ( row [0], file );
				fclose ( file );
			}

			sprintf ( str_tmp, "%sData/%d/output.in", OJ_Home, pid );
			file = fopen ( str_tmp, "r" );
			if ( file == NULL )
			{
				file = fopen ( str_tmp, "w" );
				fputs ( row [0], file );
				fclose ( file );
			}

			mysql_free_result ( res );
			return 0;
		}

		sprintf ( str_tmp, "%s/Data/%d/update.txt", OJ_Home, pid );
		file = fopen ( str_tmp, "w" );
		fputs ( row [2], file );
		fclose ( file );

		sprintf ( str_tmp, "%s/Data/%d/input.in", OJ_Home, pid );
		file = fopen ( str_tmp, "w" );
		fputs ( row [0], file );
		fclose ( file );

		sprintf ( str_tmp, "%s/Data/%d/output.in", OJ_Home, pid );
		file = fopen ( str_tmp, "w" );
		fputs ( row [1], file );
		fclose ( file );

		mysql_free_result ( res );
		return 0;
	}
	fclose ( file );
	mysql_free_result ( res );
	return 0;

}

int CreateWorkDir ()	//创建临时目录用于编译，运行,更改工作目录
{
	tmpnam ( Work_Dir );
	for ( int i=0; Work_Dir[i]; i++ )
		if ( Work_Dir [i] == '/' )	Work_Dir [i] = 'K';
	sprintf ( str_tmp, "%s/Tmp/%s", OJ_Home, Work_Dir );
	int ret = mkdir ( str_tmp, 0777 );
	if ( ret )	return ret;
	chdir ( str_tmp );
	return 0;
}

int ComplieSourceFile ()		//读取并且编译源代码
{
	sprintf ( str_tmp, "UPDATE think_solution SET result=%d WHERE solution_id=%d", OJ_Compling,solutionid );
	mysql_query ( mysql, str_tmp );

	const char * CPP[] = { "g++", "Main.cpp", "-o", "Main","-O2", "-Wall",
                    "-lm", "--static", "-DONLINE_JUDGE", NULL };

    //const char * CPP[] = { "g++", "Main.cpp", NULL };
    //g++ Main.cpp -o Main -O2 -Wall -lm --static -DONLINE_JUDGE
    sprintf ( str_tmp, "SELECT source FROM think_source_code WHERE solution_id=%d", solutionid );
	MYSQL_RES *res;
	MYSQL_ROW row;
	FILE *file;

	file = fopen ( "Main.cpp", "w" );
	if ( file == NULL )	return 1;

	int ret = mysql_query ( mysql, str_tmp );
	if ( ret )	return 1;
	res = mysql_store_result( mysql );
	row = mysql_fetch_row ( res );
	fputs ( row [0], file );
	mysql_free_result ( res );
	fclose ( file );

	int pid,status;

	pid = fork ();

	if ( pid==0 )
	{
        struct rlimit LIM;
        LIM.rlim_max = 60;
        LIM.rlim_cur = 60;
        setrlimit(RLIMIT_CPU, &LIM);

        LIM.rlim_max = 90 * MILLION;
        LIM.rlim_cur = 90 * MILLION;
        setrlimit(RLIMIT_FSIZE, &LIM);

        LIM.rlim_max = 1024 * MILLION;
        LIM.rlim_cur = 1024 * MILLION;
        setrlimit(RLIMIT_AS, &LIM);
		freopen ( "ce.txt", "w", stderr );
		execvp ( CPP[0], (char * const *)CPP );
		exit (0);
	}
	else
	{
		waitpid(pid, &status, 0);
		if ( status )
		{
			sprintf ( str_tmp, "UPDATE think_solution SET result=%d WHERE solution_id=%d", OJ_CE, solutionid );
			mysql_query ( mysql, str_tmp );

			sprintf ( str_tmp, "SELECT solution_id FROM think_compileinfo WHERE solution_id=%d", solutionid );
			mysql_query ( mysql, str_tmp );
			res = mysql_store_result( mysql );
			file = fopen ( "ce.txt", "r" );
			char* str = new char [MILLION];
			char* query = new char [2*MILLION];
			fread ( str, 1, MILLION-1, file );

			if ( res->row_count==0 )
			{
				sprintf ( query, "INSERT INTO think_compileinfo (solution_id,error) VALUES (%d,'%s')", solutionid, str );
				mysql_real_query ( mysql, query, strlen(query) );
			}
			else
			{
				sprintf ( query, "UPDATE think_compileinfo SET error='%s' WHERE solution_id=%d", str, solutionid );
				mysql_query ( mysql, query );
			}
			delete []query;
			delete []str;
			fclose ( file );
			mysql_free_result ( res );

			exit (0);
		}
		//printf("status=%d\n", status);
		//return status;
	}

	sprintf ( str_tmp, "DELETE FROM think_compileinfo WHERE solution_id=%d", solutionid );
	mysql_query ( mysql, str_tmp );
	return 0;
}

void Call_Exit ( int signal )
{
	mysql_close ( mysql );
	exit (0);
}

int Execute_SQL ( const char* query )	//用于执行非查询类的 SQL语句
{
	if ( mysql==NULL )	return 1;
	return mysql_query ( mysql, query );
}

int Execute_CMD (const char * format, ...)
{
	char cmd[BUFFER_SIZE];
	int ret = 0;
	va_list ap;
	va_start(ap, format);

	vsprintf(cmd, format, ap);
	ret = system(cmd);
	va_end(ap);
	return ret;
}

int GetFileSize(const char * filename)
{
        struct stat f_stat;
        if (stat(filename, &f_stat) == -1) {
                return 0;
        }
        return f_stat.st_size;
}

int Run ( int &usedtime, int &usedmem )
{
	MYSQL_RES *res;
	MYSQL_ROW row;
	int Mem_Limit,Time_Limit;
	int pid;
	int status;
	int ACflag=OJ_RE;
	struct rusage rusage;
	usedtime = usedmem = 0;
	int mem=0;

	Execute_CMD ( "cp %s/Data/%d/input.in ./",  OJ_Home, problemid );	//拷贝测试文件
	sprintf ( str_tmp, "SELECT memory_limit,time_limit FROM think_problem WHERE problem_id=%d", problemid );
	mysql_query ( mysql, str_tmp );
	res = mysql_store_result ( mysql );
	row = mysql_fetch_row(res);

	sscanf ( row[0], "%d", &Mem_Limit );
	sscanf ( row[1], "%d", &Time_Limit );
	if ( Mem_Limit<0 ||  Mem_Limit>256 )
		Mem_Limit = 256;
	if ( Time_Limit<0 || Time_Limit>256 )
		Time_Limit = 100;

	mysql_free_result ( res );

	//printf ( "%d %d\n", Mem_Limit, Time_Limit );

	sprintf ( str_tmp, "UPDATE think_solution SET result=%d WHERE solution_id=%d", OJ_Run_Judge, solutionid );
	mysql_query ( mysql, str_tmp );		//修改状态为 Running&&Judging

	pid = fork ();
	if ( pid==0 )
	{
		LimitResource ( Time_Limit, Mem_Limit );

        freopen("input.in", "r", stdin);
        freopen("user.out", "w", stdout);
        freopen("error.out", "w", stderr);
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("./Main", "./Main", (char *)NULL);
        exit (0);
	}
	else
	{
		while (1)
		{
			wait4 ( pid, &status, 0, &rusage );
			if ( GetFileSize ("error.out") )
			{
				ptrace(PTRACE_KILL, pid, NULL, NULL);
				return OJ_RE;
			}
			if ( WIFEXITED (status) )
				break;

			if ( WEXITSTATUS(status) && WEXITSTATUS(status) != SIGTRAP )	//退出代码  SIGTRAP 为进入断点
			{
				cout << WEXITSTATUS(status) << endl;
				switch ( WEXITSTATUS(status) )
				{
				case SIGALRM:
				case SIGXCPU:
				case SIGKILL:
					ACflag = OJ_TLE;
					break;
				case SIGXFSZ:
					ACflag = OJ_OLE;
					break;
				default:
					ACflag = OJ_RE;
					break;
				}
				ptrace(PTRACE_KILL, pid, NULL, NULL);
				return ACflag;
			}
			if (WIFSIGNALED (status))			//因信号结束程序
			{
				cout << WTERMSIG (status) << endl;
				switch ( WTERMSIG (status) )
				{
				case SIGALRM:
				case SIGXCPU:
				case SIGKILL:
					ACflag = OJ_TLE;
					break;
				case SIGXFSZ:
					ACflag = OJ_OLE;
					break;
				default:
					ACflag = OJ_RE;
					break;
				}
				ptrace(PTRACE_KILL, pid, NULL, NULL);
				return ACflag;
			}

			mem = GetPeakMem ( pid );  //单位kb
			//cout << mem << " "<<(Mem_Limit<<10) << endl;
			if ( mem > ((Mem_Limit<<10)) )
			{
				return OJ_MLE;
			}

			ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

		}

	}

	usedmem = mem;
    usedtime += (rusage.ru_utime.tv_sec * 1000 + rusage.ru_utime.tv_usec / 1000);
    usedtime += (rusage.ru_stime.tv_sec * 1000 + rusage.ru_stime.tv_usec / 1000);
    ptrace(PTRACE_KILL, pid, NULL, NULL);
	return OJ_AC;

}

void LimitResource ( int Time_Limit, int Mem_Limit )
{
	struct rlimit LIM;
	LIM.rlim_cur = Time_Limit;
	LIM.rlim_max = LIM.rlim_cur;
	setrlimit(RLIMIT_CPU, &LIM);
    alarm(0);
    alarm(Time_Limit*10);
    //限制时间

    LIM.rlim_max = (MILLION<<5)+MILLION;
    LIM.rlim_cur = (MILLION<<5);
    setrlimit(RLIMIT_FSIZE, &LIM);
    //限制文件

    LIM.rlim_cur=LIM.rlim_max=1;
    setrlimit(RLIMIT_NPROC, &LIM);
    //限制过程

    LIM.rlim_cur = MILLION << 6;
    LIM.rlim_max = MILLION << 6;
    setrlimit(RLIMIT_STACK, &LIM);
    //限制栈大小

    LIM.rlim_cur = MILLION *Mem_Limit/2*3;
    LIM.rlim_max = MILLION *Mem_Limit*2;
    setrlimit(RLIMIT_AS, &LIM);
    //限制内存大小
}

int GetPeakMem ( int pid )		//获取进程使用内存峰值 单位kb
{
	FILE *file;
	int ans=0;
	sprintf ( str_tmp, "/proc/%d/status", pid );
	file = fopen ( str_tmp, "r" );
	if ( file==NULL )	return -1;
	while ( fgets ( str_tmp, BUFFER_SIZE-1, file )!=NULL )
	{
		if ( strncmp ( "VmPeak:", str_tmp, sizeof("VmPeak:")-1 ) == 0 )
		{
			sscanf ( str_tmp+sizeof("VmPeak:")-1, "%d", &ans );
			return ans;
		}
	}
	fclose (file);
	return 0;
}

int Compare ()
{
	FILE *file1,*file2;
	int ch1,ch2;
	int ret=OJ_AC;
	sprintf ( str_tmp, "%s/Data/%d/output.out", OJ_Home, problemid );
	file1 = fopen ( "user.out", "r" );
	file2 = fopen ( str_tmp, "r" );

	if ( !file1 || !file2 )	return OJ_ERROR;
	if ( GetFileSize ("user.out") > (GetFileSize (str_tmp)*2) )	//用户输出数据大于标准答案两倍
		return OJ_OLE;

	while ( 1 )
	{
		ch1 = fgetc ( file1 );
		ch2 = fgetc ( file2 );
		while ( ch1=='\r' && ch1!=EOF )	ch1 = fgetc ( file1 );
		while ( ch2=='\r' && ch2!=EOF)	ch2 = fgetc ( file2 );

		if ( ch1!=ch2 )
		{
			if ( ch1!='\n'&& ch1!=' ' && ch2!='\n' && ch2!=' ' )
			{
				ret = OJ_WA;
				break;
			}
			else
			{
				while ( ch1=='\n' || ch1==' ' )
				{
					ch1 = fgetc ( file1 );
					if ( ch1 == EOF )	break;
				}
				while ( ch2=='\n' || ch2==' ' )
				{
					ch2 = fgetc ( file2 );
					if ( ch2 == EOF )	break;
				}
				if ( ch1==ch2 )
				{
					ret = OJ_PE;
					continue;
				}
				else
				{
					ret = OJ_WA;
					break;
				}
			}
		} else if ( ch1==ch2 && ch1==EOF )
			break;
	}

	return ret;

}
